from django.shortcuts import render
from django.views import View
from django.http import JsonResponse
# authenticate功能：传入用户名和密码，认证用户
from django.contrib.auth import authenticate
from django.conf import settings

from django.core.mail import send_mail
from django_redis import get_redis_connection
from users.models import User,Address
from users.utils import generate_verify_email_url
from celery_tasks.email.tasks import send_verify_email
from carts.utils import merge_cart_cookie_to_redis

import logging
logger = logging.getLogger('django')
# Create your views here.


# 接口：判断用户名是否重复注册
class UsernameCountView(View):

    def get(self, request, username):
        # 1、提取参数
        # 2、校验参数
        # 3、业务数据处理 —— 根据用户名查询用户数量
        try:
            count = User.objects.filter(username=username).count()
        except Exception as e:
            # 记录日志
            logger.error(e)
            # 构建错误响应
            return JsonResponse({
                'code': 400,
                'errmsg': '数据库异常'
            })
        # 4、构建响应
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'count': count
        })


# 接口：判断手机号重复注册
class MobileCountView(View):

    def get(self, request, mobile):
        # 1、提取参数
        # 2、校验参数
        # 3、业务数据处理 —— 根据手机号过滤用户数量
        try:
            count = User.objects.filter(mobile=mobile).count()
        except Exception as e:
            logger.error(e)
            return JsonResponse({
                'code': 400,
                'errmsg': '数据库访问错误。'
            })
        # 4、构建响应
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'count': count
        })


import json,re
# login函数功能：记录用户身份信息作状态保持
# logout函数功能：删除用户身份信息(清除用户session)
from django.contrib.auth import login,logout
# 接口：用户注册
class RegisterView(View):

    def post(self, request):
        # 1、提取参数
        # request.body ==> b'{"username": "weiwei".....}'
        # request.body.decode() => '{"username": "weiwei".....}'
        data = json.loads(request.body.decode()) # {"username": "weiwei".....}

        username = data.get('username') # 字典get函数，如果key不存在返回默认None
        password = data.get('password')
        password2 = data.get('password2')
        mobile = data.get('mobile')
        sms_code = data.get('sms_code')
        allow = data.get('allow') # 本身就是逻辑值

        # 2、校验参数
        # 2.1、必要性校验
        if not all([username, password, password2, mobile, sms_code]):
            return JsonResponse({
                'code': 400,
                'errmsg': '缺少必传参数'
            })

        # 2.2、格式校验
        # 用户名：[a-zA-Z0-9_-]{5,20}
        if not re.match('^[a-zA-Z0-9_-]{5,20}$', username):
            return JsonResponse({
                'code': 400,
                'errmsg': '用户名格式有误'
            })
        # 手机号：1[3-9]\d{9}
        if not re.match('^1[3-9]\d{9}$', mobile):
            return JsonResponse({
                'code': 400,
                'errmsg': '手机号格式有误'
            })
        # allow：True or False
        if not isinstance(allow, bool):
            return JsonResponse({'code': 400, 'errmsg': '请勾选协议'})
        # 业务需求allow必须是True才允许注册
        if not allow:
            return JsonResponse({'code': 400, 'errmsg': '请勾选协议'})

        # 2.3、业务性校验
        # 两次输入密码是否一致
        if password != password2:
            return JsonResponse({
                'code': 400, 'errmsg': '两次输入密码有误'
            })
        # TODO: 此处将来补充验证短信验证码逻辑代码
        conn = get_redis_connection('verify_code')
        sms_code_from_redis = conn.get('sms_%s'%mobile)
        if not sms_code_from_redis:
            return JsonResponse({'code': 400, 'errmsg': '短信验证码失效'})
        if sms_code != sms_code_from_redis.decode():
            return JsonResponse({'code': 400, 'errmsg': '短信验证码有误'})

        # 3、业务数据处理 —— 新建User对象，保存数据库
        # User.objects.create() --> 针对用户模型类，create函数不会对密码加密处理
        # User.objects.create_user() --> 针对用户模型类，create_user函数会对密码加密处理
        try:
            user = User.objects.create_user(
                username=username,
                password=password,
                mobile=mobile
            )
        except Exception as e:
            logger.error(e)
            return JsonResponse({
                'code': 400,
                'errmsg': '数据库新建用户失败'
            })

        # TODO: 用户注册/登陆成功之后，需要记录用户身份信息 —— 补充状态保持逻辑代码
        # 把用户user身份信息写入session缓存(redis的1号库中)
        # 功能：状态保持
        # 参数：request请求对象，user用户模型类对象
        # 返回值：无
        login(request, user)

        # 4、构建响应
        response = JsonResponse({
            'code': 0,
            'errmsg': 'ok'
        })
        # TODO: 合并购物车
        response = merge_cart_cookie_to_redis(request, response)
        return response





# 接口：用户传统登陆
class LoginView(View):

    def post(self, request):
        # 1、提取参数
        data = json.loads(request.body.decode())
        username = data.get('username')
        password = data.get('password')
        remembered = data.get('remembered', False)

        # 2、校验参数
        if not all([username, password]):
            return JsonResponse({
                'code': 400,
                'errmsg': '缺少参数'
            })
        if not re.match('^[a-zA-Z0-9_-]{5,20}$', username):
            return JsonResponse({
                'code': 400,
                'errmsg': '用户名格式有误'
            })
        if not re.match('^[a-zA-Z0-9_]{8,20}$', password):
            return JsonResponse({
                'code': 400,
                'errmsg': '密码格式有误'
            })

        # 3、业务数据处理
        # (1)、根据username查询用户
        # try:
        #     user = User.objects.get(username=username)
        # except User.DoesNotExist as e:
        #     logger.info(e)
        #     return JsonResponse({'code': 400, 'errmsg': '用户名或密码有误'})
        # # (2)、校验密码
        # # AbstractUser提供核心函数check_password,传入明文密码，判断密码是否正确。返回值True表示正确。
        # if not user.check_password(password):
        #     return JsonResponse({'code': 400, 'errmsg': '用户名或密码有误'})

        # TODO: 通过修改User.USERNAME_FIELD字段，来控制后续过滤用户对象使用的关键字,实现多账号登陆
        # 默认情况下全局authenticate函数使用的认证后端ModelBackend是根据关键字username进行过滤的。
        # if re.match('^1[3-9]\d{9}$', username):
        #     # 如果前端传来的账号是用户名，控制ModelBackend使用username关键字过滤
        #     User.USERNAME_FIELD = 'mobile'
        # else:
        #     # 如果前端传来的账号是手机号，控制ModelBackend使用mobile关键字过滤
        #     User.USERNAME_FIELD = 'username'

        # TODO: 把用户名密码的校验使用authenticate全局函数来完成
        # 功能：校验用户名和密码
        # 参数：request请求对象，username用户名和password密码
        # 返回值：校验成功返回用户对象，否则返回None
        # 注意事项：全局authenticate函数的关键字参数只能是username和password，此处的关键字username可以理解为"账号"
        user = authenticate(request, username=username, password=password)
        if user is None:
            return JsonResponse({'code': 400, 'errmsg': '用户名或密码有误'})

        # 状态保持 —— 把用户数据写入session缓存
        login(request, user)

        # remembered参数为True表明长期状态保持，如果为False表明用户关闭浏览器则登陆失效
        # 本质，设置用户的session数据的有效期
        if remembered != True:
            request.session.set_expiry(0) # session在页面关闭立刻失效
        else:
            request.session.set_expiry(None)  # 默认2周有效

        # 4、构建响应
        response = JsonResponse({'code': 0, 'errmsg': 'ok'})
        # TODO: 在cookie中记录username，作页面的展示
        response.set_cookie('username', user.username, max_age=3600 * 24 * 14)

        # TODO: 合并购物车
        response = merge_cart_cookie_to_redis(request, response)

        return response



# 接口：登出
class LogoutView(View):

    def delete(self, request):
        # 1、提取参数
        # 2、校验参数
        # 3、业务数据处理 —— 删除用户状态保持数据(session数据)
        # TODO: 把用户的session数据清除
        # logout函数逻辑：传入request请求对象，提取cookie中的sessionid，然后清除session缓存数据
        logout(request)
        # 4、构建响应
        response = JsonResponse({'code': 0, 'errmsg': 'ok'})
        # TODO: 删除cookie中用于页面展示的username
        response.delete_cookie('username')
        return response


# 接口：用户中心(获取用户信息)
# class UserInfoView(View):
#     def get(self, request):
#         # 1、提取参数
#         # 2、校验参数
#         # 3、业务数据处理 —— 判断登陆的用户
#         # 在django的视图函数中，如果登陆了则可以获取用户对象，否则是一个匿名用户
#         # django中关于用户的模型类设定：(1)、自定义的User；(2)匿名用户AnonymousUser
#         # 关键点：通过request.user属性获取当前请求的用户对象
#         # --> 如果当前请求已登陆，则request.user是User对象, user.is_authenticated = True
#         # --> 如果当前请求未登陆，则request.user是AnonymousUser对象, user.is_authenticated = False
#         user = request.user
#         if user.is_authenticated:
#             # 已登陆，构建该用户的信息返回
#             return JsonResponse({
#                 'code': 0, 'errmsg': 'ok',
#                 'info_data': {
#                     'username': user.username,
#                     'mobile': user.mobile,
#                     'email': user.email,
#                     'email_active': True # 用户模型类中后续需要添加字段email_active把表示邮箱激活状态
#                 }
#             })
#         else:
#             # 未登陆
#             return JsonResponse({
#                 'code': 400, 'errmsg': '您未登陆'
#             }, status=401)

from django.contrib.auth.mixins import LoginRequiredMixin
from meiduo_mall.utils.views import LoginRequiredJSONMixin
# LoginRequiredMixin拓展类：在视图函数调用之前，判定用户是否登陆。当且仅当用户登陆了，才会调用视图函数。
# class UserInfoView(LoginRequiredMixin, View):
class UserInfoView(LoginRequiredJSONMixin, View):

    def get(self, request):
        # 当前自定义的视图函数中，不自行判定用户是否登陆
        user = request.user
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'info_data': {
                'username': user.username,
                'mobile': user.mobile,
                'email': user.email,
                'email_active': user.email_active
            }
        })


# 接口：更新邮箱
class EmailView(LoginRequiredJSONMixin, View):

    def put(self, request):
        # 1、提取参数
        data = json.loads(request.body.decode())
        email = data.get('email')
        # 2、校验参数
        if email is None:
            return JsonResponse({'code': 400, 'errmsg': '缺少email字段'})
        if not re.match('^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$', email):
            return JsonResponse({'code': 400, 'errmsg': '邮箱格式有误'})

        # 3、业务数据处理
        # TODO：更新用户email
        user = request.user
        try:
            user.email = email
            user.email_active = False
            user.save()
        except Exception as e:
            logger.error(e)
            return JsonResponse({'code': 400, 'errmsg': '邮箱更新失败'})

        # TODO：发送验证邮件 —— 后续封装异步任务实现
        verify_url = generate_verify_email_url(request)
        send_verify_email.delay(email, verify_url)

        # 4、构建响应
        return JsonResponse({'code': 0, 'errmsg': 'ok'})



# 接口：激活邮箱 —— 当用户点击激活链接当时候发出的
class VerifyEmailView(View):

    def put(self, request):
        # 1、提取参数
        token = request.GET.get('token')
        # 2、校验参数
        from meiduo_mall.utils.secret import SecretOauth
        data = SecretOauth().loads(token) # None表示解密失败
        if data is None:
            return JsonResponse({'code': 400, 'errmsg': '无效token'})
        # 3、业务数据处理 —— 设置email_active字段：True激活成功，False未激活
        user_id = data.get('user_id')
        try:
            user = User.objects.get(id=user_id)
        except Exception as e:
            return JsonResponse({'code': 400, 'errmsg': '无效用户'})
        user.email_active = True
        user.save()

        # 4、构建响应
        return JsonResponse({'code': 0, 'errmsg': 'ok'})



# 接口：新建收获地址
class CreateAddressView(LoginRequiredJSONMixin, View):

    def post(self, request):
        # 限制用户最多只能设置20个收获地址
        user = request.user
        count = user.addresses.filter(is_deleted=False).count()
        # count = Address.objects.filter(is_deleted=False, user=user).count()
        if count >= 20:
            return JsonResponse({'code': 400, 'errmsg': '收货地址最多20个'})

        # 1、提取参数
        json_dict = json.loads(request.body.decode())

        receiver = json_dict.get('receiver')

        # 当前新增地址关联的省市区的主键值
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')

        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 2、校验参数
        if not all([
            receiver, province_id, city_id, district_id,
            place, mobile
        ]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必要参数'})

        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return JsonResponse({'code': 400, 'errmsg': '参数mobile有误'})

        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return JsonResponse({'code': 400, 'errmsg': '参数tel有误'})
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return JsonResponse({'code': 400, 'errmsg': '参数email有误'})


        # 3、业务数据处理 —— 新建模型类对象保存数据库
        try:
            address = Address.objects.create(
                user = request.user,
                # province = <关联的省Area对象>
                # province = Area.objects.get(pk=province_id),
                # province_id = <关联省Area对象的主键值>
                province_id = province_id,
                city_id = city_id,
                district_id = district_id,
                title = receiver, # 把收货人作为默认地址标题
                receiver = receiver,
                place = place,
                mobile = mobile,
                tel = tel or '',
                email = email or ''
            )

            # 如果当前用户没有默认地址，则把新建的地址设置为默认地址
            if not user.default_address:
                user.default_address = address
                user.save()

        except Exception as e:
            logger.error(e)
            return JsonResponse({'code': 400, 'errmsg': '新建收获地址失败'})


        # 4、构建响应
        # 新增地址成功，将新增的地址响应给前端实现局部刷新
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,

            # address地址模型类对象
            # address.province地址对象关联的省Area对象
            # address.province.name是关联省Area对象的name属性
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,

            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'address': address_dict
        })


# 接口：展示收获地址 —— 查询当前登陆用户的收货地址
class AddressView(LoginRequiredJSONMixin, View):

    # GET + /addresses/
    def get(self, request):
        # 1、提取参数
        user = request.user
        # 2、校验参数
        # 3、业务数据处理
        addresses = user.addresses.filter(is_deleted=False)

        addresses_list = []
        for address in addresses:
            # address是每一个地址对象
            addresses_list.append({
                'id': address.id,
                'title': address.title,
                'receiver': address.receiver,
                'province': address.province.name,
                'city': address.city.name,
                'district': address.district.name,
                'place': address.place,
                'mobile': address.mobile,
                'tel': address.tel or '',
                'email': address.email or ''
            })

        # 4、构建响应
        return JsonResponse({
            'code': 0, 'errmsg': 'ok',
            'default_address_id': user.default_address.id if user.default_address else None,
            'addresses': addresses_list
        })



class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):

    # 接口：修改收货地址
    #  PUT + /addresses/<被修改的地址主键值>/
    def put(self, request, address_id):
        # 1、提取参数
        json_dict = json.loads(request.body.decode())

        receiver = json_dict.get('receiver')

        # 当前新增地址关联的省市区的主键值
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')

        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 2、校验参数
        if not all([
            receiver, province_id, city_id, district_id,
            place, mobile
        ]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必要参数'})

        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return JsonResponse({'code': 400, 'errmsg': '参数mobile有误'})

        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return JsonResponse({'code': 400, 'errmsg': '参数tel有误'})
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return JsonResponse({'code': 400, 'errmsg': '参数email有误'})

        # 3、业务数据处理 —— 更新模型对象数据
        try:
            address = Address.objects.get(pk=address_id, is_deleted=False)
        except Address.DoesNotExist as e:
            return JsonResponse({'code': 400, 'errmsg': '地址不存在或已删除'})

        address.receiver = receiver
        address.province_id = province_id
        address.city_id = city_id
        address.district_id = district_id
        address.place = place
        address.mobile = mobile
        address.tel = tel or ''
        address.email = email or ''

        address.save()

        # 构造响应数据
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # 4、构建响应返回
        return JsonResponse({
            'code': 0, 'errmsg': 'ok',
            'address': address_dict
        })


    # 接口：删除收货地址(逻辑删除)
    # DELETE + /addresses/<被修改的地址主键值>/
    def delete(self, request, address_id):
        user = request.user
        try:
            address = Address.objects.get(pk=address_id)
        except Address.DoesNotExist as e:
            return JsonResponse({'code': 400, 'errmsg': '地址不存在'})
        
        # 逻辑删除
        address.is_deleted = True
        address.save()

        # 拓展：加入删除地址刚好是用户默认地址
        if user.default_address.id == address.id:
            # 思路1：把默认地址设置为None
            # user.default_address = None
            # user.save()
            # 思路2：把最新更新的地址设置为默认
            q = Address.objects.filter(user=user, is_deleted=False).order_by('-update_time')
            if q:
                user.default_address = q[0]
                user.save()

        return JsonResponse({'code': 0, 'errmsg': 'ok'})



# 接口：设置新的默认地址
class DefaultAddressView(LoginRequiredJSONMixin, View):

    # PUT + /address/<新地址主键值>/default/
    def put(self, request, address_id):
        try:
            address = Address.objects.get(
                pk=address_id, is_deleted=False
            )
        except Address.DoesNotExist as e:
            return JsonResponse({'code': 400, 'errmsg': '地址不存在'})

        user = request.user
        user.default_address = address
        user.save()

        return JsonResponse({'code': 0, 'errmsg': 'ok'})



# 接口：更新地址标题title
class UpdateTitleAddressView(View):

    # PUT + /addresses/<被更新地址主键值>/title/
    def put(self, request, address_id):

        data = json.loads(request.body.decode())
        title = data.get('title')
        if not title:
            return JsonResponse({'code': 400, 'errmsg': '缺少必要字段'})
        if len(title) > 20:
            return JsonResponse({'code': 400, 'errmsg': '标题长度不能超过20个字符'})

        try:
            address = Address.objects.get(
                pk=address_id, is_deleted=False
            )
        except Address.DoesNotExist as e:
            return JsonResponse({'code': 400, 'errmsg': '地址不存在'})

        address.title = title
        address.save()

        return JsonResponse({'code': 0, 'errmsg': 'ok'})


# 接口：更新用户密码
class ChangePasswordView(LoginRequiredJSONMixin, View):

    # PUT + /password/
    def put(self, request):
        # 1、提取参数
        user = request.user  # 必然是登陆的User对象

        data = json.loads(request.body.decode())
        old_password = data.get('old_password')
        new_password = data.get('new_password')
        new_password2 = data.get('new_password2')

        # 2、校验参数
        if not all([old_password, new_password, new_password2]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必传字段'})

        if not re.match('^[a-zA-Z0-9_-]{8,20}$', new_password):
            return JsonResponse({'code': 400, 'errmsg': '密码格式有误'})

        if new_password != new_password2:
            return JsonResponse({'code': 400, 'errmsg': '密码输入不一致'})

        if not user.check_password(old_password):
            return JsonResponse({'code': 400, 'errmsg': '密码输入有误'})

        # 3、业务数据处理 —— 把新密码写入数据库
        # new_password = '12345678' 前端传来的是明文密码，数据库是加密的pbkdf2_sha256$150000$ExWN72bdKyEF$NDyPSko1k/g7aOYcUl0TkeO0a5v3P/86BC1Jb5wGo3I=
        # user.set_password() --> 加密密码
        # user.check_password() --> 校验密码
        user.set_password(new_password)
        user.save()

        # 清除session和cookie
        logout(request)
        # 4、构建响应
        response =  JsonResponse({'code': 0, 'errmsg': 'ok'})
        response.delete_cookie('username')
        return response


from goods.models import SKU,GoodsVisitCount
from django.utils import timezone
class UserBrowseHistory(LoginRequiredJSONMixin, View):

    # 记录用户浏览历史
    # POST + /browse_histories/
    def post(self, request):
        # 1、提取参数
        data = json.loads(request.body.decode())
        sku_id = data.get('sku_id')
        # 2、校验参数
        if not sku_id:
            return JsonResponse({'code': 400, 'errmsg': '缺少参数'})
        try:
            sku = SKU.objects.get(pk=sku_id, is_launched=True)
        except SKU.DoesNotExist as e:
            return JsonResponse({'code': 400, 'errmsg': '参数有误'})
        # 3、业务数据处理 —— redis写入浏览历史
        user = request.user
        conn = get_redis_connection('history')
        p = conn.pipeline()
        # =======先去重========
        p.lrem('history_%s'%user.id, 0, sku_id)
        # =======再存储========
        p.lpush('history_%s'%user.id, sku_id)
        # =======再截取========
        p.ltrim('history_%s'%user.id, 0, 4)
        p.execute()

        # TODO: 记录当前sku商品分类的访问量——存在则累加count，不存在则新建count初始化为1
        cur_0_time = timezone.localtime().replace(hour=0, minute=0, second=0)
        try:
            gv = GoodsVisitCount.objects.get(
                category=sku.category,
                create_time__gte=cur_0_time
            )
            # 当前分类访问信息存在则累加count
            gv.count += 1
            gv.save()

        except GoodsVisitCount.DoesNotExist as e:
            # 当前分类访问信息不存在则新建count初始化为1
            gv = GoodsVisitCount.objects.create(
                count=1,
                category=sku.category
            )


        # 4、构建响应
        return JsonResponse({'code': 0, 'errmsg': 'ok'})

    # 查询浏览历史
    # GET + /browse_histories/
    def get(self, request):
        # 1、提取参数
        user = request.user
        # 2、校验参数
        # 3、业务数据处理
        conn = get_redis_connection('history')
        # =======读取redis浏览历史======
        sku_ids = conn.lrange('history_%s'%user.id, 0, -1)
        # =======获取每一个SKU对象======
        skus = []
        for sku_id in sku_ids:
            # sku_id每一个sku商品的id值
            sku = SKU.objects.get(pk=sku_id)
            # =======获取SKU详细信息构造响应参数======
            skus.append({
                'id': sku.id,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'price': sku.price
            })

        # 4、构建响应
        return JsonResponse({'code':0, 'errmsg': 'ok', 'skus': skus})





